package wx

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"io"
	"log/slog"
	"math/rand"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"gitee.com/gxcc/go-utils/utils"
	"github.com/beevik/etree"
)

type WxPaySvc struct {
	MchId     string
	PayKey    string
	NotifyUrl string
	CertPath  string
	KeyPath   string
	//CAPath    string
	tlsConfig *tls.Config
}

// GenPaySign 生成微信支付签名
func (w *WxPaySvc) GenPaySign(params map[string]string) string {
	var keys []string
	for key, value := range params {
		if len(value) > 0 {
			keys = append(keys, key)
		}
	}
	sort.Strings(keys)
	var paramStr = ""
	for _, key := range keys {
		if key == "sign" || key == "paySign" {
			continue
		}
		if len(paramStr) > 0 {
			paramStr += "&"
		}
		paramStr += key + "=" + params[key]
	}
	paramStr += "&key=" + w.PayKey

	slog.Debug("genPaySign", "origin", paramStr)
	sign := strings.ToUpper(utils.MD5(paramStr))
	slog.Debug("genPaySign", "result", sign)

	return sign
}

// GenPaySignForApp 生成支付签名
func (w *WxPaySvc) GenPaySignForApp(appId string, timestamp string, nonceStr string, prepayId string, packageStr string) string {
	var params = map[string]string{
		"appid":     appId,
		"partnerid": w.MchId,
		"prepayid":  prepayId,
		"package":   packageStr,
		"noncestr":  nonceStr,
		"timestamp": timestamp,
		//"signType":  "MD5",
	}
	return w.GenPaySign(params)
}

// GenPaySignForWXMP 生成小程序支付签名
func (w *WxPaySvc) GenPaySignForWXMP(appId string, timestamp string, nonceStr string, prepayId string, packageStr string) string {
	var params = map[string]string{
		"appId":     appId,
		"nonceStr":  nonceStr,
		"package":   packageStr,
		"signType":  "MD5",
		"timeStamp": timestamp,
	}
	return w.GenPaySign(params)
}

// 生成基础参数
func (w *WxPaySvc) genBaseParams(appId string) map[string]string {
	return map[string]string{
		"appid":     appId,
		"mch_id":    w.MchId,
		"nonce_str": strconv.Itoa(rand.Intn(100000000)),
	}
}

// MapToXml map to xml
func (WxPaySvc) MapToXml(params map[string]string) string {
	doc := etree.NewDocument()
	xml := doc.CreateElement("xml")
	for key, value := range params {
		xml.CreateElement(key).SetText(value)
	}
	str, err := doc.WriteToString()
	if err != nil {
		return ""
	}
	return str
}

// XmlToMap xml to map
func (WxPaySvc) XmlToMap(str string) map[string]string {
	strMap := map[string]string{}
	doc := etree.NewDocument()
	if err := doc.ReadFromString(str); err != nil {
		return strMap
	}

	root := doc.SelectElement("xml")
	for _, item := range root.ChildElements() {
		strMap[item.Tag] = item.Text()
	}
	return strMap
}

// Post请求
func (w *WxPaySvc) post(url string, params map[string]string) (map[string]string, error) {
	var sign = w.GenPaySign(params)
	params["sign"] = sign

	slog.Debug("post", "url", url)
	slog.Debug("post", "body", w.MapToXml(params))

	res, err := utils.HttpPost(url, map[string]string{
		"Content-Type": "application/xml",
	}, []byte(w.MapToXml(params)))

	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}

	result := string(res.Body)
	resultMap := w.XmlToMap(result)

	if strings.ToUpper(resultMap["result_code"]) == "FAIL" && len(resultMap["err_code_des"]) > 0 {
		return resultMap, errors.New(resultMap["err_code_des"])
	}
	if strings.ToUpper(resultMap["return_code"]) != "SUCCESS" || strings.ToUpper(resultMap["result_code"]) == "FAIL" {
		return resultMap, errors.New(resultMap["return_msg"])
	}

	return resultMap, nil
}

// Post请求，需指定证书
func (w *WxPaySvc) postWithCert(url string, params map[string]string) (map[string]string, error) {
	var sign = w.GenPaySign(params)
	params["sign"] = sign

	// tls证书
	tlsConfig, err := w.getTLSConfig()
	if err != nil {
		return nil, err
	}
	tr := &http.Transport{
		MaxIdleConns:       10,
		IdleConnTimeout:    30 * time.Second,
		DisableCompression: true,
		TLSClientConfig:    tlsConfig,
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Post(url, "application/xml", bytes.NewBuffer([]byte(w.MapToXml(params))))
	if err != nil {
		return nil, err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			slog.Error(err.Error())
		}
	}(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return nil, errors.New("请求失败：" + resp.Status)
	}

	body, _ := io.ReadAll(resp.Body)
	result := string(body)
	slog.Debug("postWithCert", "result", result)

	resultMap := w.XmlToMap(result)

	if strings.ToUpper(resultMap["result_code"]) == "FAIL" && len(resultMap["err_code_des"]) > 0 {
		return resultMap, errors.New(resultMap["err_code_des"])
	}
	if strings.ToUpper(resultMap["return_code"]) != "SUCCESS" || strings.ToUpper(resultMap["result_code"]) == "FAIL" {
		return resultMap, errors.New(resultMap["return_msg"])
	}

	return resultMap, nil
}

func (w *WxPaySvc) getTLSConfig() (*tls.Config, error) {
	if w.tlsConfig != nil {
		return w.tlsConfig, nil
	}

	// load cert
	cert, err := tls.LoadX509KeyPair(w.CertPath, w.KeyPath)
	if err != nil {
		return nil, err
	}

	pool := x509.NewCertPool()
	w.tlsConfig = &tls.Config{
		Certificates:       []tls.Certificate{cert},
		RootCAs:            pool,
		InsecureSkipVerify: true,
	}
	return w.tlsConfig, nil
}

// Prepay 微信预下单
// 返回map：prepay_id:预支付交易会话标识,code_url:二维码链接
func (w *WxPaySvc) Prepay(appId string, body string, attach string, outTradeNo string, totalFee int64,
	spbillCreateIP string, timeExpire time.Time, tradeType string, productId string,
	openid string) (map[string]string, error) {
	var params = w.genBaseParams(appId)
	params["body"] = body
	//params["detail"] = ""
	if len(attach) > 0 {
		params["attach"] = attach
	}
	params["out_trade_no"] = outTradeNo
	//params["fee_type"] = ""
	params["total_fee"] = strconv.FormatInt(totalFee, 10)
	params["spbill_create_ip"] = spbillCreateIP
	params["time_start"] = time.Now().Format("20060102150405")
	params["time_expire"] = timeExpire.Format("20060102150405")
	//params["goods_tag"] = goodsTag
	params["notify_url"] = w.NotifyUrl
	params["trade_type"] = tradeType
	params["product_id"] = productId
	//params["limit_pay"] = ""
	params["openid"] = openid

	var url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
	resultMap, err := w.post(url, params)
	if err != nil {
		return resultMap, err
	}

	// 失败
	if resultMap["result_code"] == "FAIL" {
		return resultMap, errors.New(resultMap["err_code_des"])
	}

	return resultMap, nil
}

// QueryWXOrder 查询微信订单
func (w *WxPaySvc) QueryWXOrder(appId string, outTradeNo string) (map[string]string, error) {
	var params = w.genBaseParams(appId)
	params["out_trade_no"] = outTradeNo
	var url = "https://api.mch.weixin.qq.com/pay/orderquery"
	return w.post(url, params)
}

// CloseWXOrder 关闭微信订单
func (w *WxPaySvc) CloseWXOrder(appId string, outTradeNo string) (map[string]string, error) {
	var params = w.genBaseParams(appId)
	params["out_trade_no"] = outTradeNo
	var url = "https://api.mch.weixin.qq.com/pay/closeorder"
	return w.post(url, params)
}

// RefundApply 退款申请
// 默认先使用未结算金额付款，如果为结算金额不足，则使用可用余额进行付款
func (w *WxPaySvc) RefundApply(appId string, outTradeNo string, outRefundNo string,
	totalFee int64, refundFee int64) (map[string]string, error) {
	var params = w.genBaseParams(appId)
	params["out_trade_no"] = outTradeNo
	params["out_refund_no"] = outRefundNo
	params["total_fee"] = strconv.FormatInt(totalFee, 10)
	params["refund_fee"] = strconv.FormatInt(refundFee, 10)
	params["op_user_id"] = w.MchId
	var url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
	resultMap, err := w.postWithCert(url, params)
	if resultMap != nil &&
		strings.ToUpper(resultMap["result_code"]) == "FAIL" &&
		strings.ToUpper(resultMap["err_code"]) == "NOTENOUGH" {
		params["refund_account"] = "REFUND_SOURCE_RECHARGE_FUNDS"
		resultMap, err = w.postWithCert(url, params)
	}

	// 如果订单已经全额退款
	if err != nil {
		slog.Error(resultMap["err_code_des"])
		if resultMap != nil && resultMap["err_code_des"] == "订单已全额退款" {
			err = nil
		}
	}
	return resultMap, err
}

// RefundQuery 退款查询
func (w *WxPaySvc) RefundQuery(appId string, outTradeNo string) (map[string]string, error) {
	var params = w.genBaseParams(appId)
	params["out_trade_no"] = outTradeNo
	var url = "https://api.mch.weixin.qq.com/pay/refundquery"
	return w.post(url, params)
}

// PayToOpenId 企业付款给用户
func (w *WxPaySvc) PayToOpenId(appId string, outTradeNo string, openid string, amount int64, spbillCreateIp string) (map[string]string, error) {
	var params = map[string]string{
		"mch_appid":        appId,
		"mchid":            w.MchId,
		"nonce_str":        strconv.Itoa(rand.Intn(100000000)),
		"partner_trade_no": outTradeNo,
		"openid":           openid,
		"check_name":       "NO_CHECK",
		"amount":           strconv.FormatInt(amount, 10),
		"desc":             "转账",
		"spbill_create_ip": spbillCreateIp,
	}
	var url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"

	// 注：必须从运营账户中进行付款
	resultMap, err := w.postWithCert(url, params)
	//if resultMap != nil &&
	//	strings.ToUpper(resultMap["result_code"]) == "FAIL" &&
	//	strings.ToUpper(resultMap["err_code"]) == "NOTENOUGH" {
	//	params["refund_account"] = "REFUND_SOURCE_RECHARGE_FUNDS"
	//	resultMap, err = w.postWithCert(url, params)
	//}
	return resultMap, err
}

// SendRedPack 发放红包
func (w *WxPaySvc) SendRedPack(transNo string, mchName string, appId string, openId string, amount int64,
	wishing string, clientIp string, actName string, remark string) (map[string]string, error) {

	// 商户等名称不能过长
	for len(mchName) >= 32 {
		runes := []rune(mchName)
		if len(runes) > 0 {
			mchName = string(runes[:len(runes)-1])
		}
	}

	for len(actName) >= 32 {
		runes := []rune(actName)
		if len(runes) > 0 {
			actName = string(runes[:len(runes)-1])
		}
	}

	var params = map[string]string{
		"nonce_str":    strconv.Itoa(rand.Intn(100000000)),
		"mch_billno":   transNo,
		"mch_id":       w.MchId,
		"wxappid":      appId,
		"send_name":    mchName,
		"re_openid":    openId,
		"total_amount": strconv.FormatInt(amount, 10),
		"total_num":    "1",
		"wishing":      wishing,
		"client_ip":    clientIp,
		"act_name":     actName,
		"remark":       remark,
	}
	if amount < 100 || amount > 20000 {
		params["scene_id"] = "PRODUCT_4"
	}

	var url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"

	resultMap, err := w.postWithCert(url, params)
	if resultMap != nil &&
		strings.ToUpper(resultMap["result_code"]) == "FAIL" &&
		strings.ToUpper(resultMap["err_code"]) == "SYSTEMERROR" {
		params["refund_account"] = "REFUND_SOURCE_RECHARGE_FUNDS"
		resultMap, err = w.postWithCert(url, params)
	}
	if err != nil {
		return resultMap, err
	}
	// 失败
	if resultMap["result_code"] == "FAIL" {
		return resultMap, errors.New(resultMap["err_code_des"])
	}
	return resultMap, nil
}

// QueryRedPack 查询红包记录
func (w *WxPaySvc) QueryRedPack(transNo string, appId string) (map[string]string, error) {
	var params = map[string]string{
		"nonce_str":  strconv.Itoa(rand.Intn(100000000)),
		"mch_billno": transNo,
		"mch_id":     w.MchId,
		"appid":      appId,
		"bill_type":  "MCHT",
	}

	var url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo"

	resultMap, err := w.postWithCert(url, params)
	if err != nil {
		return resultMap, err
	}
	// 失败
	if resultMap["result_code"] == "FAIL" {
		return resultMap, errors.New(resultMap["err_code_des"])
	}
	return resultMap, nil
}
